﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Reflection;

namespace DynamicVm
{
    public class DynamicBindable : IDynamicMetaObjectProvider, ITriggerPropertyChanged, INotifyPropertyChanged
    {
        protected dynamic Self { get { return this; } }

        public virtual object Invoke(string name, params object[] args)
        {
            MethodInfo method = _type.GetMethod(name);
            return method.Invoke(this, args);
        }

        public virtual object GetValue(string name)
        {
            IMemberValue value;
            if (_properties.TryGetValue(name, out value))
            {
                return value.GetValue();
            }

            throw new UndefinedPropertyException(name);
        }

        public virtual object SetValue(string name, object value)
        {
            IMemberValue memberValue;
            if (_properties.TryGetValue(name, out memberValue))
            {
                memberValue.SetValue(value);
                return value;
            }
            
            memberValue = new MemberValue(this, name, value);
            _properties.Add(name, memberValue);
            return value;
        }

        protected IMemberValue GetMember(string name)
        {
            IMemberValue value;
            return _properties.TryGetValue(name, out value) ? value : null;
        }
        
        void ITriggerPropertyChanged.OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter)
        {
            return new DynamicBindableMetaObject(parameter, this);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public DynamicBindable()
        {
            _properties = new Dictionary<string, IMemberValue>();
            _type = GetType();
        }

        private readonly IDictionary<string, IMemberValue> _properties;
        private readonly Type _type;
    }
}
